Skip to content

fix: resolve local timezone to IANA name for sleep commands#21

Open
dtrinh wants to merge 1 commit intosteipete:mainfrom
dtrinh:fix/local-timezone-resolution
Open

fix: resolve local timezone to IANA name for sleep commands#21
dtrinh wants to merge 1 commit intosteipete:mainfrom
dtrinh:fix/local-timezone-resolution

Conversation

@dtrinh
Copy link
Copy Markdown

@dtrinh dtrinh commented Feb 22, 2026

Summary

  • sleep day and sleep range fail with 400 BadRequest when using the default --timezone=local because time.Local.String() returns "Local", which is not a valid IANA timezone name
  • Added resolveTimezone() helper that detects the system IANA timezone by reading the /etc/localtime symlink (macOS/Linux) or TZ env var
  • Fixes both sleep day and sleep range commands

Root Cause

The existing code did:

tz := viper.GetString("timezone")
if tz == "local" {
    tz = time.Local.String() // Returns "Local", not "America/New_York"
}

The Eight Sleep API rejects "Local" as an invalid timezone value.

Test plan

  • eightctl sleep day --quiet works without specifying --timezone
  • eightctl sleep range --from 2026-02-14 --to 2026-02-21 --quiet works without specifying --timezone
  • Explicit --timezone America/New_York still works
  • go build ./cmd/eightctl compiles cleanly

🤖 Generated with Claude Code

time.Local.String() returns "Local" on macOS/Linux, which is not a
valid IANA timezone name and gets rejected by the Eight Sleep API
with a 400 BadRequest error listing all valid timezone values.

Add resolveTimezone() which detects the system IANA timezone by
reading /etc/localtime symlink (macOS/Linux) or TZ env var.

Fixes sleep day and sleep range commands when using the default
--timezone=local setting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@omarshahine
Copy link
Copy Markdown
Collaborator

Hey @dtrinh — nice fix! The time.Local.String() returning "Local" instead of an IANA name is a real gotcha and your symlink-reading approach is the right solution.

This looks good to merge. A couple of minor notes:

  • It'll need a rebase onto current main (some upstream changes landed recently).
  • A unit test for resolveTimezone() would be a nice-to-have but not blocking.
  • No Windows support is fine — falling back to the original behavior is the right call there.

If you can rebase, I'll get this merged. Thanks for the contribution!

@omarshahine
Copy link
Copy Markdown
Collaborator

Update: PR #31 just landed with a resolveTZ() helper in the client layer that handles the "Local" → IANA resolution. It's simpler than your symlink-reading approach (falls back to UTC when time.Local.String() returns "Local"), but it covers the core bug.

Your approach with /etc/localtime symlink parsing is more robust and would catch cases where Go's time.Local.String() returns "Local" even on macOS/Linux. If you'd still like to contribute it, it would need to be retargeted as an improvement to the existing resolveTZ() function in internal/client/metrics.go. Otherwise, feel free to close this if you're satisfied with the current fix.

Either way, thanks for identifying this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants